//-----------------------------------------------------------------------------------------------
// Author: Andre Van Der Merwe <dart@pobox.com>
// Project: .net Compound File (http://dotnetpcf.sourceforge.net)
// 
// Copyright (c) Andre Van Der Merwe 2000-2001
// Licence: BSD - see the licence.txt file or visit the project homepage
//-----------------------------------------------------------------------------------------------


namespace dotnetpcf.CompoundFile
{
   using System;
   using System.Diagnostics;
   using System.Collections;
   using System.Data;
   using System.IO;

   /// <summary>
   /// Class containing system wide consts
   /// </summary>
   public class SysInfo
   {
      private SysInfo()
      {
      }

      public const int BLOCK_BYTE_SIZE = 2048;

      public const uint UNDEFINED = 0xffffffff;
      public const uint MAGIC_COMPOUNDFILE1 = 0x01234567;
      public const uint MAGIC_COMPOUNDFILE2 = 0x89abcdef;
      public const uint MAGIC_ALLOCTBL_HEADER = 0xa1b55b1a;
      public const uint MAGIC_FILETBL_HEADER = 0x70123afc;
      public const uint MAGIC_DIRTBL_HEADER = 0x19283746;
      public const uint MAGIC_NAMES_HEADER = 0x64faec70;

      public const string FILENAME_NAMES = null;
      public const string FILENAME_PROPFILE = null;
   }

   /// <summary>
   /// Possible return values
   /// </summary>
   public enum CompoundFileResult
   {
      cfrOk,
      cfrUnhandledException,
      cfrInvalidCFMagicNumbers,
      cfrInvalidAllocTableMagicNumbers,
      cfrInvalidFileTableMagicNumbers,
      cfrStreamIsNull,
      cfrLoadCheckAllocTableFail,
      cfrLoadAllocTableFailedChainingInvalid,
      cfrHeaderFileTableFileTablePosInvalid,
      cfrLoadCheckFileTableFail,
      cfrInvalidDirTableMagicNumbers,
      cfrLoadCheckDirTableFail,
      cfrInvalidNamesMagicNumbers,
      cfrInvalidDirName,
      cfrInvalidFileName
   }

   /// <summary>
   /// Custom exception class, has a CompoundFileResult member
   /// </summary>
   public class CompoundFileException : Exception
   {
      public CompoundFileException(  CompoundFileResult cfr  )
         : base()
      {
         m_CompoundFileResult = cfr;
      }
      
      public CompoundFileException(  CompoundFileResult cfr,  string sMsg  )
         : base(  sMsg  )
      {
         m_CompoundFileResult = cfr;         
      }

      public readonly CompoundFileResult m_CompoundFileResult;
   }

   /// <summary>
   /// Header block used by the tables (dir, file and alloc)
   /// Loaded/saved before the table specific headers
   /// </summary>
   public class BlockHeader
   {
      public const uint SIZE = 8;

      public uint m_iMagic = SysInfo.UNDEFINED;
      public uint m_iTotalItems = 0;

      public virtual void LoadFromStream(  Stream strm  )
      {
         Debug.Assert(  null != strm  );
         if(  null == strm  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

         BinaryReader rdr = new BinaryReader(  strm  );

         m_iMagic = rdr.ReadUInt32();
         m_iTotalItems = rdr.ReadUInt32();
      }                              

      public virtual void SaveToStream(  Stream strm  )
      {
         Debug.Assert(  null != strm  );
         if(  null == strm  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

         BinaryWriter wrtr = new BinaryWriter(  strm  );

         wrtr.Write(  m_iMagic  );
         wrtr.Write(  m_iTotalItems  );
      }
   }

   /// <summary>
   /// The compound file header
   /// </summary>
   public class CompoundFileHeader
   {
      public static uint SIZE = 52;

      public uint m_iMagic1 = SysInfo.MAGIC_COMPOUNDFILE1;
      public uint m_iMagic2 = SysInfo.MAGIC_COMPOUNDFILE2;
      public uint m_iHeaderSize = SIZE;
      public uint m_iVersion = 0x02;      //Version of this file
      public uint m_iLowVersion = 0x01;   //Lowest version able to read this file  
      public uint m_iAllocTableIndex = 0x01; 
      public uint m_iFileTableIndex = 0x02;
      public uint m_iDirTableIndex = 0x03;
      public uint m_iNamesIndex = 0x04;
      public uint m_iNextID = SysInfo.UNDEFINED; //Block index to next alloc table block
      public uint m_iPhysicalPosOfFileTable_FileTableEntry = SysInfo.UNDEFINED; //Position of the file table - See LoadFromExistingStream
      public uint m_iVirtualSizeOfFileTable = SysInfo.UNDEFINED; //Size of the file table  - See LoadFromExistingStream
      public uint m_iBlockSize = SysInfo.UNDEFINED;

      public void LoadFromStream(  Stream strm  )
      {
         Debug.Assert(  null != strm  );
         if(  null == strm  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

         BinaryReader rdr = new BinaryReader(  strm  );

         m_iMagic1 = rdr.ReadUInt32();
         m_iMagic2 = rdr.ReadUInt32();
         m_iHeaderSize = rdr.ReadUInt32();
         m_iVersion = rdr.ReadUInt32();
         m_iLowVersion = rdr.ReadUInt32();
         m_iAllocTableIndex = rdr.ReadUInt32();
         m_iFileTableIndex = rdr.ReadUInt32();
         m_iDirTableIndex = rdr.ReadUInt32();
         m_iNamesIndex = rdr.ReadUInt32();
         m_iNextID = rdr.ReadUInt32();
         m_iPhysicalPosOfFileTable_FileTableEntry = rdr.ReadUInt32();
         m_iVirtualSizeOfFileTable = rdr.ReadUInt32();
         m_iBlockSize = rdr.ReadUInt32();

         if(  (SysInfo.MAGIC_COMPOUNDFILE1 != m_iMagic1) || (SysInfo.MAGIC_COMPOUNDFILE2 != m_iMagic2)  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrInvalidCFMagicNumbers  )  );

         if(  SysInfo.UNDEFINED == m_iPhysicalPosOfFileTable_FileTableEntry  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrHeaderFileTableFileTablePosInvalid  )  );

         Debug.Assert(  m_iVersion >= m_iLowVersion,  "Low version can not be greater than current version"  );
         Debug.Assert(  SysInfo.UNDEFINED != m_iHeaderSize  );
         Debug.Assert(  SysInfo.BLOCK_BYTE_SIZE >= m_iHeaderSize  );
         Debug.Assert(  SysInfo.UNDEFINED != m_iAllocTableIndex  );
         Debug.Assert(  SysInfo.UNDEFINED != m_iFileTableIndex  );
         Debug.Assert(  SysInfo.UNDEFINED != m_iDirTableIndex  );
         Debug.Assert(  SysInfo.UNDEFINED != m_iPhysicalPosOfFileTable_FileTableEntry  );
         Debug.Assert(  SysInfo.UNDEFINED != m_iVirtualSizeOfFileTable  );
         Debug.Assert(  SysInfo.UNDEFINED != m_iNamesIndex  );
         Debug.Assert(  SysInfo.BLOCK_BYTE_SIZE == m_iBlockSize,  "incorrect block size"  );
      }


      public void SaveToStream(  Stream strm  )
      {
         Debug.Assert(  null != strm  );
         if(  null == strm  )
            throw(  new CompoundFileException(  CompoundFileResult.cfrStreamIsNull  )  );

         BinaryWriter wrtr = new BinaryWriter(  strm  );

         wrtr.Write(  m_iMagic1  );
         wrtr.Write(  m_iMagic2  );
         wrtr.Write(  m_iHeaderSize  );
         wrtr.Write(  m_iVersion  );
         wrtr.Write(  m_iLowVersion  );
         wrtr.Write(  m_iAllocTableIndex  );
         wrtr.Write(  m_iFileTableIndex  );
         wrtr.Write(  m_iDirTableIndex  );
         wrtr.Write(  m_iNamesIndex  );
         wrtr.Write(  m_iNextID  );
         wrtr.Write(  m_iPhysicalPosOfFileTable_FileTableEntry  );
         wrtr.Write(  m_iVirtualSizeOfFileTable  );
         wrtr.Write(  SysInfo.BLOCK_BYTE_SIZE  );
      }
   }

   /// <summary>
   /// Overriden hashtable class that calls a callback when it is modified
   /// </summary>
   internal class NotifyHashtable : Hashtable
   {
      public delegate void OnChangeCallBack();
      private OnChangeCallBack m_OnChange = null;

      public OnChangeCallBack OnChange
      {
         get{ return m_OnChange; }
         set{ m_OnChange = value; }
      }

      public override void Add(  object key,  object value  )
      {
         base.Add(  key,  value  );

         if(  null != m_OnChange)
            m_OnChange();
      }

      public override void Remove(  object key  )
      {
         base.Remove(  key  );

         if(  null != m_OnChange)
            m_OnChange();
      }

      public override void Clear()
      {
         base.Clear();

         if(  null != m_OnChange)
            m_OnChange();
      }
   }
}